/*
 * Copyright oVirt Authors
 * SPDX-License-Identifier: Apache-2.0
*/

package org.ovirt.engine.api.v3.helpers;

import java.util.Objects;
import java.util.Optional;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;

import org.ovirt.engine.api.model.Cluster;
import org.ovirt.engine.api.model.Properties;
import org.ovirt.engine.api.model.Property;
import org.ovirt.engine.api.model.SchedulingPolicies;
import org.ovirt.engine.api.model.SchedulingPolicy;
import org.ovirt.engine.api.resource.SchedulingPoliciesResource;
import org.ovirt.engine.api.resource.SystemResource;
import org.ovirt.engine.api.restapi.resource.BackendApiResource;
import org.ovirt.engine.api.v3.types.V3Cluster;
import org.ovirt.engine.api.v3.types.V3Fault;
import org.ovirt.engine.api.v3.types.V3SchedulingPolicy;
import org.ovirt.engine.api.v3.types.V3SchedulingPolicyThresholds;

/**
 * This class contains a set of methods useful to handle backwards compatibility issues related to clusters.
 */
public class V3ClusterHelper {
    /**
     * Makes sure that the V4 cluster has a reference to a scheduling policy that is compatible with the one specified
     * by the given V3 cluster. If that isn't possible throws an exception that will result in an HTTP error response
     * sent to the caller.
     */
    public static void assignCompatiblePolicy(V3Cluster v3Cluster, Cluster v4Cluster) {
        V3SchedulingPolicy v3Policy = v3Cluster.getSchedulingPolicy();
        SchedulingPolicy v4Policy = v4Cluster.getSchedulingPolicy();
        boolean v3IsSet = v3Policy != null && (v3Policy.isSetName() || v3Policy.isSetPolicy());
        boolean v4IsSet = v4Policy != null && (v4Policy.isSetName() || v4Policy.isSetId());
        if (v3IsSet && !v4IsSet) {
            SchedulingPolicy v4CompatiblePolicy = findCompatiblePolicy(v3Policy);
            if (v4CompatiblePolicy != null) {
                v4Policy = new SchedulingPolicy();
                v4Policy.setId(v4CompatiblePolicy.getId());
                v4Cluster.setSchedulingPolicy(v4Policy);
            } else {
                V3Fault fault = new V3Fault();
                fault.setReason("Operation Failed");
                fault.setDetail("Can't find a compatible scheduling policy.");
                Response response = Response.serverError().entity(fault).build();
                throw new WebApplicationException(response);
            }
        }
    }

    /**
     * Tries to find a V4 scheduling policy that is compatible with the given V3 scheduling policy.
     *
     * @return the compatible scheduling policy, or {@code null} if no such policy can be found
     */
    public static SchedulingPolicy findCompatiblePolicy(V3SchedulingPolicy v3Policy) {
        SystemResource systemResource = BackendApiResource.getInstance();
        SchedulingPoliciesResource policiesResource = systemResource.getSchedulingPoliciesResource();
        SchedulingPolicies v4Policies = policiesResource.list();
        return v4Policies.getSchedulingPolicies().stream()
            .filter(v4Policy -> arePoliciesCompatible(v3Policy, v4Policy))
            .findFirst()
            .orElse(null);
    }

    /**
     * Checks if the given V3 and V4 scheduling policies are compatible.
     *
     * @return {@code true} iif the two policies are compatible
     */
    private static boolean arePoliciesCompatible(V3SchedulingPolicy v3Policy, SchedulingPolicy v4Policy) {
        // The V3 "policy" attribute must be equal to the V4 "name" attribute:
        String v3Name = v3Policy.getName();
        if (v3Name == null) {
            v3Name = v3Policy.getPolicy();
        }
        if (v3Name != null && !Objects.equals(v3Name, v4Policy.getName())) {
            return false;
        }

        // All the V3 threshold must match the equivalent V4 properties:
        V3SchedulingPolicyThresholds v3Thresholds = v3Policy.getThresholds();
        Properties v4Properties = v4Policy.getProperties();
        if (v3Thresholds != null) {
            Integer v3Duration = v3Thresholds.getDuration();
            Integer v4Duration = getIntegerProperty(v4Properties, "CpuOverCommitDurationMinutes");
            if (!arePropertiesCompatible(v3Duration, v4Duration)) {
                return false;
            }
            Integer v3High = v3Thresholds.getHigh();
            Integer v4High = getIntegerProperty(v4Properties, "HighUtilization");
            if (!arePropertiesCompatible(v3High, v4High)) {
                return false;
            }
            Integer v3Low = v3Thresholds.getLow();
            Integer v4Low = getIntegerProperty(v4Properties, "LowUtilization");
            if (!arePropertiesCompatible(v3Low, v4Low)) {
                return false;
            }
        }

        // All the tests passed, so the policies are compatible:
        return true;
    }

    /**
     * Finds an integer property with the given name inside the given properties object.
     *
     * @return the value of the property, or {@code null} if there is no such property
     */
    public static Integer getIntegerProperty(Properties properties, String name) {
        if (properties == null) {
            return null;
        }
        Optional<String> text = properties.getProperties().stream()
            .filter(property -> Objects.equals(name, property.getName()))
            .map(Property::getValue)
            .findFirst();
        if (text.isPresent()) {
            return Integer.valueOf(text.get());
        }
        return null;
    }

    /**
     * Checks if two integer scheduling policy properties are compatible.
     *
     * @param v3Value the V3 value
     * @param v4Value the V4 value
     * @returns {@code true} the values are compatible, {@code false} otherwise
     */
    private static boolean arePropertiesCompatible(Integer v3Value, Integer v4Value) {
        return v3Value == null || v4Value == null || Objects.equals(v3Value, v4Value);
    }
}
